#include "ccms/nr/cpu_stats.h"
#include "arlib/raii/file_handle.h"
#include "arlib/exception/invalid_argument_exception.h"
#include "arlib/exception/errno_exception.h"
#include <cstring>
#include <cerrno>
#include <linux/version.h>

#define THROW_INVALIDARGUMENTEXCEPTION(reason) throw arlib::exception::InvalidArgumentException(__FILE__, __LINE__, reason)
#define THROW_RUNTIMEEXCEPTION(reason) throw arlib::exception::RuntimeException(__FILE__, __LINE__, reason)
#define THROW_ERRNOEXCEPTION(reason, errnum) throw arlib::exception::ErrnoException(__FILE__, __LINE__, reason, errnum)

#define BUFFER_SIZE 1024

using namespace ccms::nr;

namespace ccms {
namespace nr {

CpuStats::CpuStats(void)
{
    memset(&cpuTimeRec, '\0', sizeof(CpuTime));
    memset(&cpuTimePrev, '\0', sizeof(CpuTime));
}

void CpuStats::update(void)
{
    arlib::raii::FileHandle fh;
    char buffer[BUFFER_SIZE];
    bool scanOk = false;

    // save the most recent values
    memcpy(&cpuTimePrev, &cpuTimeRec, sizeof(CpuTime));

    // update
    fh.reset(fopen("/proc/stat", "rb"));
    if(!fh.get()) {
        THROW_ERRNOEXCEPTION("Could not open /proc/stat", errno);
    }

    do {
        memset(buffer, '\0', sizeof(buffer));
        if(!fgets(buffer, sizeof(buffer) / sizeof(char), fh.get())) {
            if(feof(fh.get())) {
                break;
            } else {    
                THROW_ERRNOEXCEPTION("Could not read from /proc/stat", ferror(fh.get()));
            }
        }
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
        if(sscanf(buffer, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
#else
        if(sscanf(buffer, "cpu %llu %llu %llu %llu %llu %llu %llu %llu",
#endif
                &cpuTimeRec.user,
                &cpuTimeRec.nice,
                &cpuTimeRec.system,
                &cpuTimeRec.idle,
                &cpuTimeRec.iowait,
                &cpuTimeRec.irq,
                &cpuTimeRec.softirq,
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
                &cpuTimeRec.steal,
                &cpuTimeRec.guest
                ) == 9) {
#else
                &cpuTimeRec.steal
                ) == 8) {
#endif
            scanOk = true;
            break;
        }
    } while(!feof(fh.get()));

    if(scanOk == false) {
        THROW_RUNTIMEEXCEPTION("Could not match data in /proc/stat");
    }
}

unsigned long long CpuStats::getCpuTimeDiff(const CpuUsageMode mode) const
{
    unsigned long long ret;

    switch(mode) {
      case CPU_USAGE_MODE_USER:
          ret = cpuTimeRec.user - cpuTimePrev.user;
          break;
      case CPU_USAGE_MODE_NICE:
          ret = cpuTimeRec.nice - cpuTimePrev.nice;
          break;
      case CPU_USAGE_MODE_SYSTEM:
          ret = cpuTimeRec.system - cpuTimePrev.system;
          break;
      case CPU_USAGE_MODE_IDLE:
          ret = cpuTimeRec.idle - cpuTimePrev.idle;
          break;
      case CPU_USAGE_MODE_IOWAIT:
          ret = cpuTimeRec.iowait - cpuTimePrev.iowait;
          break;
      case CPU_USAGE_MODE_IRQ:
          ret = cpuTimeRec.irq - cpuTimePrev.irq;
          break;
      case CPU_USAGE_MODE_SOFTIRQ:
          ret = cpuTimeRec.softirq - cpuTimePrev.softirq;
          break;
      case CPU_USAGE_MODE_STEAL:
          ret = cpuTimeRec.steal - cpuTimePrev.steal;
          break;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
      case CPU_USAGE_MODE_GUEST:
          ret = cpuTimeRec.guest - cpuTimePrev.guest;
          break;
#endif
      default:
          THROW_INVALIDARGUMENTEXCEPTION("Invalid mode");
    }

    return ret;
}

unsigned long long CpuStats::getCpuTimeDiffTotal(void) const
{
    unsigned long long ret;

    ret = (cpuTimeRec.user    - cpuTimePrev.user)
        + (cpuTimeRec.nice    - cpuTimePrev.nice)
        + (cpuTimeRec.system  - cpuTimePrev.system)
        + (cpuTimeRec.idle    - cpuTimePrev.idle)
        + (cpuTimeRec.iowait  - cpuTimePrev.iowait)
        + (cpuTimeRec.irq     - cpuTimePrev.irq)
        + (cpuTimeRec.softirq - cpuTimePrev.softirq)
        + (cpuTimeRec.steal   - cpuTimePrev.steal)
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
        + (cpuTimeRec.guest   - cpuTimePrev.guest);
#else
        ;
#endif

    return ret;
}

} // namespace nr
} // namespace ccms

